package com.liuwei.spring.cloud.framework.aspects;


import com.liuwei.spring.cloud.common.utils.Jackson;
import com.liuwei.spring.cloud.common.utils.StringPool;
import com.liuwei.spring.cloud.framework.spring.ApplicationHelper;
import com.liuwei.spring.cloud.framework.annotations.RedisLock;
import com.liuwei.spring.cloud.framework.annotations.RedisLocks;
import jakarta.validation.ValidationException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.core.StandardReflectionParameterNameDiscoverer;
import org.springframework.core.annotation.Order;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;

import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * @author : evan.liu
 * @version V1.0
 * @Description: 分布式锁切面
 * @date Date : 2022年03月08日 10:34 上午
 */

@Slf4j
@Component
@Aspect
@Order(0)
public class RedisLockAspect {

    private RedissonClient getRedissonInstance() {
        return ApplicationHelper.getBean(RedissonClient.class);
    }

    /**
     * 定义缓存逻辑
     *
     * @throws Throwable
     */
    @Around("@annotation(com.liuwei.spring.cloud.framework.annotations.RedisLocks)")
    public Object locks(ProceedingJoinPoint pjp) throws Throwable {
        Method method = getMethod(pjp);
        RedisLocks redisLocks = method.getAnnotation(RedisLocks.class);
        RedisLock[] redisLockArray = redisLocks.value();
        List<RedisLock> locks = Arrays.asList(redisLockArray);
        // 获取注解详情
        if (ArrayUtils.isEmpty(redisLockArray)) {
            throw new ValidationException("RedisLock 注解配置异常");
        }

        // 获取需要加锁的 Key 值
        Collection<String> lockingKeys = getLockingKeys(pjp, locks);
        log.info("RedisLock:{}", Jackson.toJSONString(lockingKeys));

        // 循环加多重锁
        RLock redissonLock = null;
        if (CollectionUtils.isNotEmpty(lockingKeys)) {
            RedissonClient redisson = getRedissonInstance();
            List<String> allKeys = new ArrayList<String>(lockingKeys);
            RLock[] rLocks = new RLock[allKeys.size()];
            for (int k = 0; k < allKeys.size(); k++) {
                String key = allKeys.get(k);
                rLocks[k] = redisson.getLock(key);
            }
            redissonLock = redisson.getMultiLock(rLocks);
        }

        int waitTime = locks.get(0).waitMilliseconds();
        int leaseTime = locks.get(0).expireSeconds();
        String errorMsg = locks.get(0).message();
        try {
            Assert.isTrue(ObjectUtils.isNotEmpty(redissonLock), "分布式锁构造异常");
            // 兼容多版本的 WatchDog 续期功能
            boolean locked = false;
            if (leaseTime != -1) {
                locked = redissonLock.tryLock(waitTime, leaseTime * 1000, TimeUnit.MILLISECONDS);
            } else {
                locked = redissonLock.tryLock(waitTime, TimeUnit.MILLISECONDS);
            }
            Assert.isTrue(locked, errorMsg);
            return pjp.proceed();
        } catch (Exception e) {
            log.info("RedisLock|error:{}", e.getMessage(), e);
            throw e;
        } finally {
            if (redissonLock != null){
                if (redissonLock != null){
                    try {
                        redissonLock.unlock();
                    } catch (Exception e) {
                        log.info("RedisLock|error:{}", e.getMessage(), e);
                    }
                }
            }
        }
    }


    /**
     * 定义缓存逻辑
     *
     * @throws Throwable
     */
    @Around("@annotation(com.liuwei.spring.cloud.framework.annotations.RedisLock)")
    public Object lock(ProceedingJoinPoint pjp) throws Throwable {
        Method method = getMethod(pjp);
        RedisLock cacheLock = method.getAnnotation(RedisLock.class);
        List<RedisLock> locks = Arrays.asList(cacheLock);
        // 获取需要加锁的 Key 值
        Collection<String> lockingKeys = getLockingKeys(pjp, locks);
        log.info("RedisLock:{}", Jackson.toJSONString(lockingKeys));
        // 循环加多重锁
        RLock redissonLock = null;
        if (CollectionUtils.isNotEmpty(lockingKeys)) {
            RedissonClient redisson = getRedissonInstance();
            List<String> allKeys = new ArrayList<String>(lockingKeys);
            RLock[] rLocks = new RLock[allKeys.size()];
            for (int k = 0; k < allKeys.size(); k++) {
                String key = allKeys.get(k);
                rLocks[k] = redisson.getLock(key);
            }
            redissonLock = redisson.getMultiLock(rLocks);
        }

        int waitTime = locks.get(0).waitMilliseconds();
        int leaseTime = locks.get(0).expireSeconds();
        String errorMsg = locks.get(0).message();
        try {
            Assert.isTrue(ObjectUtils.isNotEmpty(redissonLock), "分布式锁构造异常");
            // 兼容多版本的 WatchDog 续期功能
            boolean locked = false;
            if (leaseTime != -1) {
                locked = redissonLock.tryLock(waitTime, leaseTime * 1000, TimeUnit.MILLISECONDS);
            } else {
                locked = redissonLock.tryLock(waitTime, TimeUnit.MILLISECONDS);
            }
            Assert.isTrue(locked, errorMsg);
            return pjp.proceed();
        } catch (Exception e) {
            log.info("RedisLock|error:{}", e.getMessage(), e);
            throw e;
        } finally {
            if (redissonLock != null){
                try {
                    redissonLock.unlock();
                } catch (Exception e) {
                    log.info("RedisLock|error:{}", e.getMessage(), e);
                }
            }
        }
    }

    private Collection<String> getLockingKeys(ProceedingJoinPoint pjp, List<RedisLock> annotations) {
        // 获取需要加锁的 Key 值
        Collection<String> lockingKeys = new HashSet<>();
        for (RedisLock cacheLock : CollectionUtils.emptyIfNull(annotations)) {
            String prefix = cacheLock.prefix();
            boolean multiLocker = cacheLock.lockIterator();
            // 处理 Collection 多重锁
            if (multiLocker) {
                Collection locker = evaluate(pjp, cacheLock.lockKey(), Collection.class);
                // 对所有的 Collection 元素批量加锁
                if (CollectionUtils.isNotEmpty(locker)) {
                    locker.forEach(x -> {
                        lockingKeys.add(prefix + x.toString());
                    });
                } else {
                    lockingKeys.add(prefix);
                }
            } else {
                String locker = evaluate(pjp, cacheLock.lockKey(), String.class);
                String key = ObjectUtils.isEmpty(locker) ? StringPool.EMPTY : locker.toString();
                lockingKeys.add(prefix + key);
            }
        }
        return lockingKeys;
    }

    /**
     * 获取被拦截方法对象
     * <p>
     * MethodSignature.getMethod() 获取的是顶层接口或者父类的方法对象
     * 而缓存的注解在实现类的方法上
     * 所以应该使用反射获取当前对象的方法对象
     */
    private Method getMethod(ProceedingJoinPoint pjp) {
        Method method = null;

        Signature signature = pjp.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method targetMethod = methodSignature.getMethod();

        try {
            method = pjp.getTarget().getClass().getMethod(pjp.getSignature().getName(), targetMethod.getParameterTypes());
        } catch (NoSuchMethodException | SecurityException e) {
            e.printStackTrace();
        }
        return method;
    }

    /**
     * 获取缓存的key
     * key 定义在注解上，支持SPEL表达式
     *
     * @param pjp
     * @return
     * @throws NoSuchMethodException
     */
    private <T> T evaluate(ProceedingJoinPoint pjp, String key, Class<T> expectedResultType) {
        //获取被拦截方法参数名列表(使用Spring支持类库)
        ParameterNameDiscoverer u = new StandardReflectionParameterNameDiscoverer();
        Method method = getMethod(pjp);
        String[] paraNameArr = u.getParameterNames(method);
        Object[] args = pjp.getArgs();

        //使用SPEL进行key的解析
        ExpressionParser parser = new SpelExpressionParser();
        //SPEL上下文 //把方法参数放入SPEL上下文中
        StandardEvaluationContext context = new StandardEvaluationContext();
        for (int i = 0; i < paraNameArr.length; i++) {
            context.setVariable(paraNameArr[i], args[i]);
        }
        return parser.parseExpression(key).getValue(context, expectedResultType);
    }

}
